package proyectoia;

import IA.DistFS.Requests;
import IA.DistFS.Servers;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Set;
import org.apache.commons.math3.stat.descriptive.SummaryStatistics;

public class DistFileSystemState {

    //atributs de clase
    Servers servers;
    Requests requests;
    int numServers;
    /*
     * Representem les asignaciones de la següent manera: una llista de peticions
     * per cada servidor. Si tenim n servidors, la posicion n representa les
     * peticions sense servidor asignat.
     */
    ArrayList<ArrayList<Integer>> allocations;
    ArrayList<Integer> unallocated;
    int[] serverTime;

    public DistFileSystemState(Servers servers, Requests requests, int numServers) {
        //initcialitcem els atributs de clase
        this.servers = servers;
        this.requests = requests;
        this.numServers = numServers;

        //initcialitcem els atributs d'instancia
        this.allocations = new ArrayList();
        this.unallocated = new ArrayList();
        this.serverTime = new int[numServers];
        for (int i = 0; i < numServers; i++) {
            serverTime[i] = 0;
            allocations.add(new ArrayList<Integer>());
        }
    }

    public DistFileSystemState(DistFileSystemState parent) {
        this.servers = parent.servers;
        this.requests = parent.requests;
        this.numServers = parent.numServers;
        this.allocations = new ArrayList(numServers);
        this.serverTime = new int[numServers];
        this.unallocated = new ArrayList();
        for (int i = 0; i < numServers; i++) {
            this.serverTime[i] = parent.serverTime[i];
            allocations.add(new ArrayList<Integer>());
            for (Integer asig : parent.allocations.get(i)) {
                this.allocations.get(i).add(asig);
            }
        }
        for (Integer req : parent.unallocated) {
            this.unallocated.add(req);
        }
    }

    /*
     * El caracter # s'utilitza com separador. Posteriorment sera utilitzat
     * al fer split.
     */
    @Override
    public String toString() {
        String s = new String();
        for (int i = 0; i < numServers; i++) {
            s = s.concat("Servidor " + i + " Temps " + getServerTime(i) + " ms ");
            s = s.concat("Peticions");
            for (Integer req : allocations.get(i)) {
                s = s.concat(" " + req + " (" + servers.tranmissionTime(i, requests.getRequest(req)[0]) + " ms),");
            }
        }
        return s;
    }

    public void print() {
        // COMENTARIS: Chorrada.. pero ja que ho fem en la funcio toString pq ho tornem a fer... jeje ;)
        System.out.println("Asignacions");
        for (int i = 0; i < numServers; i++) {
            System.out.println("    Servidor " + i + " -> " + getServerTime(i) + " ms");
            System.out.print("        Peticions: ");
            for (Integer req : allocations.get(i)) {
                System.out.print(" " + req + " (" + servers.tranmissionTime(i, requests.getRequest(req)[0]) + " ms),");
            }
            System.out.println("");
        }
    }

    /*
     * @s
     * Retorna el temps total del servidor s
     */
    public int getServerTime(int s) {
        return serverTime[s];
    }

    public int getSlowestServerTime() {
        return getServerTime(getSlowestServer());
    }

    public List<Integer> getServerTimesList() {
        List<Integer> times = new ArrayList();
        for (int i = 0; i < numServers; i++) {
            times.add(getServerTime(i));
        }
        return times;
    }
    
    public double getServerTimeStandardDeviation() {
        SummaryStatistics ss = new SummaryStatistics();
        for (int i = 0; i < numServers; i++) {
            ss.addValue(getServerTime(i));
        }
        return ss.getStandardDeviation();
    }
    
    public int getTotalServerTime() {
        int total = 0;
        for (int i = 0; i < numServers; i++) {
            total += getServerTime(i);
        }
        return total;
    }

    public int getSlowestServer() {
        int slowestServer = 0;
        for (int i = 1; i < numServers; i++) {
            if (serverTime[i] > serverTime[slowestServer]) {
                slowestServer = i;
            }
        }
        return slowestServer;
    }

    public boolean isAllocatable(Integer request, Integer server) {
        /*
         * Mirar si el fitxer de la request està en el server
         */
        int file = getFile(request);
        Set<Integer> fileLocations = servers.fileLocations(file);
        return fileLocations.contains(server);
    }

    /*
     * Asigna cada peticio al servidor que menys augmenta el seu temps de treball
     */
    public void generateGreedySolution() {
        for (int i = 0; i < requests.size(); i++) {
            int user = getUser(i);
            int file = getFile(i);

            int tmax = Integer.MAX_VALUE; //
            Integer bestServer = 0;
            for (int server : servers.fileLocations(file)) {
                int time = servers.tranmissionTime(server, user);
                if (time + serverTime[server] < tmax) {
                    tmax = time + serverTime[server];
                    bestServer = server;
                }
            }
            serverTime[bestServer] = tmax;
            allocations.get(bestServer).add(i);
        }
    }

    public void generateRandomSolution(long seed) {
        Random rand = new Random(seed);
        for (int i = 0; i < requests.size(); i++) {
            int user = getUser(i);
            int file = getFile(i);
            Object[] locations = servers.fileLocations(file).toArray();
            int server = (Integer) locations[rand.nextInt(locations.length)];
            serverTime[server] += servers.tranmissionTime(server, user);
            allocations.get(server).add(i);
        }
    }

    public void generateEmptySolution() {
        for (int i = 0; i < requests.size(); i++) {
            unallocated.add(i);
        }
    }

    public ArrayList<ArrayList<Integer>> getAllocations() {
        return allocations;
    }

    public int getUser(Integer request) {
        return requests.getRequest(request)[0];
    }

    public int getFile(Integer request) {
        return requests.getRequest(request)[1];
    }

    public Set<Integer> getFileLocations(Integer file) {
        return servers.fileLocations(file);
    }

    private int getTranmissionTime(Integer server, Integer request) {
        return servers.tranmissionTime(server, getUser(request));
    }

    public String addAllocationOperator(Integer request, Integer server) {
        if (this.unallocated.remove(request)) {
            this.allocations.get(server).add(request);
            this.serverTime[server] += getTranmissionTime(server, request);
            return "addAllocationOperator," + request + "," + server + ";";
        } else {
            System.out.println("Invalid request allocation:" + request + "," + server + ";");
            return "nop";
        }
    }

    public String removeAllocationOperator(Integer request, Integer server) {
        if (this.allocations.get(server).remove(request)) {
            this.serverTime[server] -= getTranmissionTime(server, request);
            this.unallocated.add(request);
            return "removeAllocationOperator," + request + "," + server + ";";
        } else {
            System.out.println("Invalid request removal." + request + "," + server + ";");
            return "nop";
        }
    }

    public String changeAllocationOperator(Integer request, Integer oldServer, Integer newServer) {
        return removeAllocationOperator(request, oldServer) + addAllocationOperator(request, newServer);
    }

    public String swapAllocationsOperator(Integer req1, Integer req2, Integer server1, Integer server2) {
        return changeAllocationOperator(req1, server1, server2) + changeAllocationOperator(req2, server2, server1);
    }
    
    public void parseAction(String action) throws Exception {
        String[] ops = action.split(";");
        for (int i = 0; i < ops.length; i++) {
            String[] args = ops[i].split(",");
            if (args[0].equals("addAllocationOperator")) {
                this.addAllocationOperator(Integer.parseInt(args[1]), Integer.parseInt(args[2]));
            } else if (args[0].equals("removeAllocationOperator")) {
                this.removeAllocationOperator(Integer.parseInt(args[1]), Integer.parseInt(args[2]));
            } else {
                throw new Exception("Error parsing action: " + action);
            }
        }
    }

    public void parseActionList(List<String> actions) throws Exception {
        for (String action : actions) {
            parseAction(action);
        }
    }
}
